﻿using System;
using System.Windows;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using Bling.DSL;
using Bling.WPF;
using Bling.Util;
using Bling.Physics;
using Bling.Animation;
using Bling.Shapes;

namespace Bling.PhysicsTutorial {
  public static class CollisionInverseExtensions {
    public static IntBl AdjacentX(this PhysicsBlock<CollisionInverse.CustomBlock>.Body body) {
      return body.GetProperty(body.Block.AdjacentXProperty);
    }
    public static IntBl AdjacentY(this PhysicsBlock<CollisionInverse.CustomBlock>.Body body) {
      return body.GetProperty(body.Block.AdjacentYProperty);
    }
  }
  public class CollisionInverse : PhysicsCanvas {
    public class SnowBlock : ImmediatePhysicsBlock<SnowBlock>, ImmediateRender.Is<SnowBlock>, ParticleSystem.Is<SnowBlock> {
      private readonly CanvasBl MainCanvas;
      private readonly PointBl Shake;
      internal readonly PhysicsProperty<bool> VDirectionProperty;
      internal readonly PhysicsProperty<bool> HDirectionProperty;
      public SnowBlock(World World, int Count, CanvasBl MainCanvas, PointBl Shake)
        : base(World, Count) {
        VDirectionProperty = this.NewBodyProperty<bool>("VDirection");
        HDirectionProperty = this.NewBodyProperty<bool>("HDirection");
        this.MainCanvas = MainCanvas;
        this.Shake = Shake;
        new ImmediateRender.Impl<SnowBlock>(this, MainCanvas);
        new ParticleSystem.Impl<SnowBlock>(this) {
          EmitNew = (b) => 1d,
          InitEmit = (block) => {
            DoubleBl r = new Random();
            var InitCenter = PointBl.Assign<int>((i) => block[i].CenterX(), (i) => Shake + new PointBl(r * MainCanvas.Width, r * MainCanvas.Height));
            var InitOldCenter = PointBl.Assign<int>((i) => block[i].CenterX().Old, (i) => block[i].CenterX() + new PointBl(-100 + r * 200,-100 + r * 200));
            var InitTotalAge = DoubleBl.Assign<int>((i) => block[i].TotalAge(), (i) => r * 5000);
            var InitAge = DoubleBl.Assign<int>((i) => block[i].Age(), (i) => block[i].TotalAge());
            return (idx) => {
              InitCenter(idx);
              InitOldCenter(idx);
              InitTotalAge(idx);
              InitAge(idx);
            };
          },
          //Decay = (body) => 0d,
        };
      }
      protected override void Init() {
        base.Init();
        ForAll(body => {
          body.Radius().Init = 5d;
          body.Brush().Init = Brushes.White;

          body.CenterX().VerletStep(.1);
          var sz = MainCanvas.Size;
          sz = new PointBl((sz.X == 0).Condition(1d, sz.X), (sz.Y == 0).Condition(1d, sz.Y));
          body.CenterX().Bind = body.CenterX().Clamp(Shake, MainCanvas.Size + Shake);
          body.CenterX().Bind += 
            new PointBl(body.GetProperty(HDirectionProperty).Bl().Condition(+1, -1), body.GetProperty(VDirectionProperty).Bl().Condition(+1, -1)) * (1d - body.AgeRatio());

          body.GetProperty(VDirectionProperty).Bl().Bind = (body.CenterX().Y > MainCanvas.Size.Y + Shake.Y).Condition(
            false, (body.CenterX().Y < Shake.Y).Condition(true, body.GetProperty(VDirectionProperty)));

          body.GetProperty(HDirectionProperty).Bl().Bind = (body.CenterX().X > MainCanvas.Size.X + Shake.X).Condition(
            false, (body.CenterX().X < Shake.X).Condition(true, body.GetProperty(HDirectionProperty)));

          //body.Center().Relax[1d - body.AgeRatio()] = new PointBl(body.Center().X, MainCanvas.Height + Shake.Y);
        });
      }
      protected override void Activate() {
        base.Activate();
      }
    }


    public class CustomBlock : PhysicsBlock<CurvedPolygon, CurvedPolygonBl, CustomBlock>,
      ParticleBody.Is<CustomBlock>, RigidBody.Is<CustomBlock>, ShapeBody.Is<CustomBlock>, MouseAdapter.Is<CurvedPolygon, CustomBlock>, CollisionBody.Is<CustomBlock> {

      internal readonly PhysicsProperty<int> AdjacentXProperty;
      internal readonly PhysicsProperty<int> AdjacentYProperty;
      internal readonly PhysicsProperty<Point> FlockCenterProperty;

      public PointBl FlockCenter {
        get { return this.GetProperty(FlockCenterProperty); }
        set { FlockCenter.Bind = value; }
      }
      public CustomBlock(World World, int Count, Func<int, CurvedPolygon> MakeT)
        : base(World, Count, MakeT) {
        AdjacentXProperty = this.NewBodyProperty<int>("AdjacentX");
        AdjacentYProperty = this.NewBodyProperty<int>("AdjacentY");
        //ScaleProperty = this.NewBodyProperty<double>("Scale");
        FlockCenterProperty = this.NewBlockProperty<Point>("FlockCenter");
        // Must manually install particle body and shape body extensions. 
        new ParticleBody.Impl<CustomBlock>(this) { Loss = .1 };
        new RigidBody.Impl<CustomBlock>(this);
        new CollisionBody.Impl<CustomBlock>(this);
        new ShapeBody.Impl<CustomBlock>(this) {
          //Scale = (body) => new PointBl(body.ScaleA(), body.ScaleA()),
          ShapeStrength = body => .5,
        };
        new MouseAdapter.Impl<CurvedPolygon, CustomBlock>(this);
      }
      protected override void Activate() {
        base.Activate();
        var DeltaF = PointBl.Func<int, int>((i, j) => this[j].Center() - this[i].Center());
      }
      protected override void Init() {
        base.Init();
        ForAll((CustomBlock.Body body) => {
          // equal to myself.
          body.AdjacentX().Init = body.Index;
          body.AdjacentY().Init = body.Index;
          body.Scale().Init = 1d;

          //body.ScaleA().Bind = (body.ScaleA() + (body.At.IsMouseOver.Condition(+.1, -.01))).Clamp(1d,2d);
          body.Scale().Bind = ((body.At.IsMouseOver.Condition(2d, 1d)));

        });
      }
    }

    static CollisionInverse() {
      ParticleBody.Impl<CustomBlock>.ParticlesCount = 4;
    }
    public CollisionInverse()
      : base("CollisionInverse", 30) {
      Background = Brushes.LightGray;
    }
    protected override Action Init(CanvasBl MainCanvas, World World) {
      var Count = 30;
      var canvas0 = new CanvasBl(MainCanvas) {
        LeftTop = Shake * -1d,
        Size = MainCanvas.Size,
      };
      var Strength = new LabelSliderBl(MainCanvas) {
        ZIndex = 1, Width = MainCanvas.Width, RightBottom = MainCanvas.Size,
        Value = 0.02, Minimum = 0, Maximum = 1, LabelName = "Strength",
      };
      var Separate = new LabelSliderBl(MainCanvas) {
        ZIndex = 1, Width = MainCanvas.Width, RightBottom = Strength.RightTop,
        Value = 10, Minimum = 0, Maximum = 100, LabelName = "Separate",
      };


      CustomBlock block = new CustomBlock(World, Count, (idx) => {
        var ret = new CurvedPolygonBl(canvas0, 4) {
          Fill = idx.SelectColor(), Smoothness = 0,
        };
        return ret;
      }) { Name = "CI", };
      SnowBlock snow = new SnowBlock(World, 200, canvas0, Shake);

      // create a collision manager
      var Collisions = new BodyCollisionManager(canvas0, World);

      block.ShapePoints().BindRect(new PointBl(100,100));
      block.CustomInit = (block0) => {
        block0.ForAll((CustomBlock.Body body) => {
          for (int i = 0; i < 4; i++) {
            body.Particles()[i].Position.Link.To =  body.At.Points[i];
          }
          body.Particles().ForAll(particle => {
            PointBl r = new Random();
            particle.Position.Init = (new PointBl(1000, 1000) * r);
            var p = particle.Position;
            p.Bind = p.Clamp(new PointBl(0, 0) + Shake, MainCanvas.Size + Shake);
          });

          ObjectBl tooltip = "IDX=".Bl() + body.Index + " X=".Bl() + 
            body.AdjacentX().ToStringBl() + " Y=".Bl() + body.AdjacentY().ToStringBl();
          tooltip.Link.To = body.At.ToolTip;

          //((ObjectBl) body.AdjacentX().ToStringBl()).Link.To = body.At.ToolTip;
        });
      };
      block.ShapeBodyE().RelaxNext = (block0) => {
        var p = new PointBl(0, 0);
        for (int i = 0; i < Count; i++) p += block0[i].Center();
        p = p / Count;
        block0.FlockCenter.Bind = p;
        block0.ForAll(body => {
          body.Rotation().Relax[Strength] = 0.ToPI();
          // travel up and left.
          // work on up first.

          var bodyX = block0[body.AdjacentX()];
          var bodyY = block0[body.AdjacentY()];
          if (false) {
            var pX = bodyX.Center();
            var pY = bodyY.Center();

            var lX = (pX - body.Center()).LengthSquared;
            var lY = (pY - body.Center()).LengthSquared;

            var isX = bodyX.Index != body.Index;
            var isY = bodyY.Index != body.Index;

            var b = 50d * body.Scale();

            pX = pX + new PointBl(50d * bodyX.Scale() + Separate + b, -50d * bodyX.Scale() + b);
            pY = pY + new PointBl(-50d * bodyY.Scale() + b, 50d * bodyY.Scale() + Separate + b);

            var goal = (isX & isY).Condition((pX + pY) / 2d, isX.Condition(pX, isY.Condition(pY,
              Shake + new PointBl(Separate + b, Separate + b))));
            //isX = isX & (!isY | lX < lY);
            //isY = isY & !isX;

            //var goal = isX.Condition(pX, isY.Condition(pY, Shake + new PointBl(Separate + b, Separate + b)));

            body.Center().Relax[Strength] = goal;


          } else {



            DoubleBl Up = (body.Index == bodyY.Index).Condition(Shake.Y,
              bodyY.Center().Y + 50d * bodyY.Scale());

            DoubleBl Left = (body.Index == bodyX.Index).Condition(Shake.X,
              bodyX.Center().X + 50d * bodyX.Scale());

            Up = Up + 50d * body.Scale() + Separate.Value;
            Left = Left + 50d * body.Scale() + Separate.Value;
            body.Center().Relax[Strength] = new PointBl(Left, Up);
          }
        });
      };
      return () => {
        Collisions.Add(block);

        {
          Action<int, Point> Assign = PointBl.Assign<int, Point>((i,d) => snow[i].CenterX(), (i,d) => snow[i].CenterX() + d);

          Action<int, int, Point> AssignB = PointBl.Assign<int, int, Point>((i, j, d) => block[i].Particles()[j], 
                                                                            (i, j, d) => block[i].Particles()[j].Position + d);


          Func<int, double> StartX = DoubleBl.Func<int>(i => snow[i].CenterX().X - snow[i].Radius());
          Func<int, double> EndX = DoubleBl.Func<int>(i => snow[i].CenterX().X + snow[i].Radius());



          Func<int, int, Point> Base = PointBl.Func<int, int>((snowI, bodyI) => {
            PointBl v = null;
            DoubleBl l = 0d;
            for (int i = 0; i < 4; i++) {
              var b = snow[snowI].CenterX().TriangleBase(block[bodyI].Particles()[(i + 0) % 4],
                                                        block[bodyI].Particles()[(i + 1) % 4]);
              b = b - snow[snowI].CenterX();
              var l0 = b.LengthSquared;
              if (((object)v) == null) {
                l = l0; v = b;
              } else {
                l = (l0 < l).Condition(l0, l);
                v = (l0 < l).Condition(b, v);
              }
            }
            return v;
          });
          Func<int, int, int, Point> Base0 = PointBl.Func<int, int, int>((snowI, bodyI, vertexI) => {
            {
              var i = vertexI.Bl();
              var b = snow[snowI].CenterX().TriangleBase(block[bodyI].Particles()[(i + 0) % 4],
                                                        block[bodyI].Particles()[(i + 1) % 4]);
              b = b - snow[snowI].CenterX();
              return b;
            }
          });
          Func<int, int, int, bool> Intersect0 = BoolBl.Func<int, int, int>((snowI, bodyI, vertexI) => {
            var snowP = snow[snowI].CenterX();
            var i = vertexI.Bl();
            var particles = block[bodyI].Particles();
            var p0 = particles[(i + 0) % 4];
            var p1 = particles[(i + 1) % 4];
            var p2 = particles[(i + 2) % 4];
            return snowP.SameSide(p2, p0, p1);
          });



          var SnowXSort = new SweepList<int>(null);
          SnowXSort.Init(StartX, EndX);
          Func<int, double> StartY = DoubleBl.Func<int>(i => snow[i].CenterX().Y - snow[i].Radius());
          Func<int, double> EndY = DoubleBl.Func<int>(i => snow[i].CenterX().Y + snow[i].Radius());
          Action<int> FlipV = BoolBl.Assign<int>(i => snow[i].GetProperty(snow.VDirectionProperty), i =>
            !snow[i].GetProperty(snow.VDirectionProperty).Bl());
          Action<int> FlipH = BoolBl.Assign<int>(i => snow[i].GetProperty(snow.HDirectionProperty), i =>
            !snow[i].GetProperty(snow.HDirectionProperty).Bl());

          for (int i = 0; i < snow.Count; i++) {
            SnowXSort.Insert(i);
          }
          World.AfterRelax.Add(() => {
            for (int i = 0; i < 3; i++) {
              if (!SnowXSort.Sort()) break;
            }
            Collisions.Prepare();
            int x = 0;
            SnowXSort.Sweep<BaseCollisionBody>(Collisions.XSort, (i, j) => {
              if (i.AABB.Right <= StartX(j)) return +1;
              if (i.AABB.X >= EndX(j)) return -1;
              return 0;
            }, (i, j) => {
              if (StartY(i) >= j.AABB.Y && EndY(i) <= j.AABB.Bottom) {
                var v0 = (Vector)Base0(i, j.CoreIndex, 0);
                var v1 = (Vector)Base0(i, j.CoreIndex, 1);
                var v2 = (Vector)Base0(i, j.CoreIndex, 2);
                var v3 = (Vector)Base0(i, j.CoreIndex, 3);

                var l0 = v0.LengthSquared;
                var l1 = v1.LengthSquared;
                var l2 = v2.LengthSquared;
                var l3 = v3.LengthSquared;

                Vector v = v0;
                var l = l0;
                var k = 0;
                if (l1 < l) { v = v1; l = l1; k = 1; }
                if (l2 < l) { v = v2; l = l2; k = 2; }
                if (l3 < l) { v = v3; l = l3; k = 3; }

                if (Intersect0(i, j.CoreIndex, k) && l > 0) {
                  // extend v by 5
                  var lX = Math.Sqrt(l);
                  v = v / lX * (lX + 5);



                  // check to see if its really penatrated


                  //var v0 = Base(i, j.CoreIndex);
                  // divide by two
                  Assign(i, (Point)((v) * .975));
                  if (k % 2 == 0)
                    FlipV(i);
                  else FlipH(i);

                  var w = (Point)((v) * -.025);
                  AssignB(j.CoreIndex, (k + 0) % 4, w);
                  AssignB(j.CoreIndex, (k + 1) % 4, w);
                  x++;
                }
              }
              return;
            });
            (x >= 0).Assert();
          });
        }
        {
          Func<int, double> StartX = DoubleBl.Func<int>(i => block[i].Center().X - 40d * block[i].Scale());
          Func<int, double> StartY = DoubleBl.Func<int>(i => block[i].Center().Y - 40d * block[i].Scale());
          Func<int, double> EndX = DoubleBl.Func<int>(i => block[i].Center().X + 40d * block[i].Scale());
          Func<int, double> EndY = DoubleBl.Func<int>(i => block[i].Center().Y + 40d * block[i].Scale());
          Action<int, int> SetAdjacentX = IntBl.Assign<int, int>((i, j) => block[i].AdjacentX(), (i, j) => j);
          Action<int, int> SetAdjacentY = IntBl.Assign<int, int>((i, j) => block[i].AdjacentY(), (i, j) => j);

          var XSort = new SweepList<int>(null);
          XSort.Init(StartX, EndX);
          var YSort = new SweepList<int>(null);
          YSort.Init(StartY, EndY);
          for (int i = 0; i < block.Count; i++) {
            YSort.Insert(i);
            XSort.Insert(i);
          }
          double[] trackX = new double[block.Count];
          double[] trackY = new double[block.Count];
          World.AfterRelax.Add(() => {
            while (true) if (!YSort.Sort()) break;
            while (true) if (!XSort.Sort()) break;
            for (int i = 0; i < block.Count; i++) {
              trackY[i] = 0d;
              trackX[i] = 0d;
              SetAdjacentX(i, i);
              SetAdjacentY(i, i);
            }
            YSort.Sweep(YSort.CanCollide, (bodyA, bodyB) => {
              if (EndX(bodyB) > trackX[bodyA] + 50d && EndX(bodyB) < EndX(bodyA)) {
                SetAdjacentX(bodyA, bodyB);
                trackX[bodyA] = EndX(bodyB);
              } else if (EndX(bodyA) > trackX[bodyB] + 50d && EndX(bodyA) < EndX(bodyB)) {
                SetAdjacentX(bodyB, bodyA);
                trackX[bodyB] = EndX(bodyA);
              }
            });
            XSort.Sweep(XSort.CanCollide, (bodyA, bodyB) => {
              if (EndY(bodyB) > trackY[bodyA] + 50d && EndY(bodyB) < EndY(bodyA)) {
                SetAdjacentY(bodyA, bodyB);
                trackY[bodyA] = EndY(bodyB);
              } else if (EndY(bodyA) > trackY[bodyB] + 50d && EndY(bodyA) < EndY(bodyB)) {
                SetAdjacentY(bodyB, bodyA);
                trackY[bodyB] = EndY(bodyA);
              }
            });
          });
        }
      };
    }
  }

}
